{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "cee85286-fcfe-452b-9017-7c0bce8168bf",
   "metadata": {},
   "outputs": [],
   "source": [
    "from faichem.core.common.relaxation.ase_utils import OCPCalculator\n",
    "import ase.io\n",
    "from ase.optimize import BFGS\n",
    "\n",
    "from fairchem.data.oc.core import Adsorbate, AdsorbateSlabConfig, Bulk, Slab\n",
    "import os\n",
    "from glob import glob\n",
    "import pandas as pd\n",
    "from fairchem.data.oc.utils import DetectTrajAnomaly\n",
    "from fairchem.data.oc.utils.vasp import write_vasp_input_files\n",
    "\n",
    "# Optional - see below\n",
    "import numpy as np\n",
    "from dscribe.descriptors import SOAP\n",
    "from scipy.spatial.distance import pdist, squareform\n",
    "from x3dase.visualize import view_x3d_n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a774b7f5-e662-4959-9202-660d7bcaaacd",
   "metadata": {
    "tags": []
   },
   "source": [
    "# Enumerate the adsorbate-slab configurations to run relaxations on"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6b10e112-25be-43e2-8f7d-0cda02187746",
   "metadata": {},
   "source": [
    "Be sure to set the path to the bulk and adsorbate pickle files in `ocdata/configs/paths.py` or pass the paths as an argument. The database pickles can be found in `ocdata/databases/pkls`. AdsorbML incorporates random placement, which is especially useful for more complicated adsorbates which may have many degrees of freedom. I have opted sample a few random placements and a few heuristic. Here I am using *CO on copper (1,1,1) as an example."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "6c7132ce-9536-4b5f-a7f7-cf42a528b312",
   "metadata": {},
   "outputs": [],
   "source": [
    "bulk_src_id = \"mp-30\"\n",
    "adsorbate_smiles = \"*CO\"\n",
    "\n",
    "bulk = Bulk(bulk_src_id_from_db = bulk_src_id)\n",
    "adsorbate = Adsorbate(adsorbate_smiles_from_db=adsorbate_smiles)\n",
    "slabs = Slab.from_bulk_get_specific_millers(bulk = bulk, specific_millers=(1,1,1))\n",
    "\n",
    "# There may be multiple slabs with this miller index.\n",
    "# For demonstrative purposes we will take the first entry.\n",
    "slab = slabs[0]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "d834adae-9d08-4e6f-9ada-23fc7c6b67d1",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Perform heuristic placements\n",
    "heuristic_adslabs = AdsorbateSlabConfig(slabs[0], adsorbate, mode=\"heuristic\")\n",
    "\n",
    "# Perform random placements\n",
    "# (for AdsorbML we use `num_sites = 100` but we will use 4 for brevity here)\n",
    "random_adslabs = AdsorbateSlabConfig(slabs[0], adsorbate, mode=\"random_site_heuristic_placement\", num_sites = 4)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "31df1a82-46b1-4ab1-8c2c-e4983fe31747",
   "metadata": {},
   "source": [
    "# Run ML relaxations:\n",
    "\n",
    "There are 2 options for how to do this.\n",
    " 1. Using `OCPCalculator` as the calculator within the ASE framework\n",
    " 2. By writing objects to lmdb and relaxing them using `main.py` in the ocp repo\n",
    " \n",
    "(1) is really only adequate for small stuff and it is what I will show here, but if you plan to run many relaxations, you should definitely use (2). More details about writing lmdbs has been provided [here](https://github.com/Open-Catalyst-Project/ocp/blob/main/tutorials/lmdb_dataset_creation.ipynb) - follow the IS2RS/IS2RE instructions. And more information about running relaxations once the lmdb has been written is [here](https://github.com/Open-Catalyst-Project/ocp/blob/main/TRAIN.md#initial-structure-to-relaxed-structure-is2rs).\n",
    "\n",
    "You need to provide the calculator with a path to a model checkpoint file. That can be downloaded [here](../core/model_checkpoints)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "fe297959-7de2-4e54-bca6-562b230de12c",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "WARNING:root:Skipping scheduler setup. No training set found.\n",
      "WARNING:root:No seed has been set in modelcheckpoint or OCPCalculator! Results may not be reproducible on re-run\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "      Step     Time          Energy          fmax\n",
      "BFGS:    0 13:37:56       -0.375000        1.904969\n",
      "BFGS:    1 13:37:56       -0.379639        2.309965\n",
      "BFGS:    2 13:37:56       -0.414795        0.834439\n",
      "BFGS:    3 13:37:56       -0.444092        0.828862\n",
      "BFGS:    4 13:37:56       -0.501953        1.234276\n",
      "BFGS:    5 13:37:57       -0.541016        0.238121\n",
      "BFGS:    6 13:37:57       -0.541992        0.184133\n",
      "BFGS:    7 13:37:57       -0.543457        0.231295\n",
      "BFGS:    8 13:37:57       -0.546387        0.312988\n",
      "BFGS:    9 13:37:57       -0.551270        0.339192\n",
      "BFGS:   10 13:37:57       -0.554199        0.225820\n",
      "BFGS:   11 13:37:57       -0.557129        0.114753\n",
      "BFGS:   12 13:37:57       -0.557617        0.171475\n",
      "BFGS:   13 13:37:57       -0.559570        0.206108\n",
      "BFGS:   14 13:37:57       -0.560547        0.224397\n",
      "BFGS:   15 13:37:57       -0.561523        0.172185\n",
      "BFGS:   16 13:37:58       -0.562988        0.080907\n",
      "BFGS:   17 13:37:58       -0.563965        0.068554\n",
      "BFGS:   18 13:37:58       -0.565430        0.098937\n",
      "BFGS:   19 13:37:58       -0.566406        0.126637\n",
      "BFGS:   20 13:37:58       -0.567383        0.088776\n",
      "BFGS:   21 13:37:58       -0.567383        0.052951\n",
      "BFGS:   22 13:37:58       -0.567383        0.048906\n",
      "      Step     Time          Energy          fmax\n",
      "BFGS:    0 13:37:58       -0.436768        1.854050\n",
      "BFGS:    1 13:37:58       -0.438965        2.270738\n",
      "BFGS:    2 13:37:58       -0.471680        0.716368\n",
      "BFGS:    3 13:37:58       -0.487793        0.670602\n",
      "BFGS:    4 13:37:58       -0.525879        1.202618\n",
      "BFGS:    5 13:37:59       -0.552246        0.646694\n",
      "BFGS:    6 13:37:59       -0.555664        0.191207\n",
      "BFGS:    7 13:37:59       -0.556641        0.130377\n",
      "BFGS:    8 13:37:59       -0.557129        0.227302\n",
      "BFGS:    9 13:37:59       -0.558594        0.247916\n",
      "BFGS:   10 13:37:59       -0.561035        0.175825\n",
      "BFGS:   11 13:37:59       -0.564941        0.070150\n",
      "BFGS:   12 13:37:59       -0.565430        0.063138\n",
      "BFGS:   13 13:37:59       -0.565430        0.209048\n",
      "BFGS:   14 13:37:59       -0.565430        0.210213\n",
      "BFGS:   15 13:37:59       -0.567383        0.104561\n",
      "BFGS:   16 13:38:00       -0.567383        0.043487\n",
      "      Step     Time          Energy          fmax\n",
      "BFGS:    0 13:38:00       -0.354980        1.328401\n",
      "BFGS:    1 13:38:00       -0.364258        1.643524\n",
      "BFGS:    2 13:38:00       -0.395752        1.080726\n",
      "BFGS:    3 13:38:00       -0.508789        0.621278\n",
      "BFGS:    4 13:38:00       -0.510742        0.221040\n",
      "BFGS:    5 13:38:00       -0.509277        0.240734\n",
      "BFGS:    6 13:38:00       -0.509766        0.433013\n",
      "BFGS:    7 13:38:00       -0.513184        0.405498\n",
      "BFGS:    8 13:38:00       -0.517578        0.215517\n",
      "BFGS:    9 13:38:00       -0.518066        0.087934\n",
      "BFGS:   10 13:38:00       -0.519043        0.208171\n",
      "BFGS:   11 13:38:01       -0.520508        0.339821\n",
      "BFGS:   12 13:38:01       -0.521484        0.318497\n",
      "BFGS:   13 13:38:01       -0.522949        0.177809\n",
      "BFGS:   14 13:38:01       -0.523438        0.046033\n",
      "      Step     Time          Energy          fmax\n",
      "BFGS:    0 13:38:01       -0.232544        1.082081\n",
      "BFGS:    1 13:38:01       -0.259033        1.503252\n",
      "BFGS:    2 13:38:01       -0.356689        1.815192\n",
      "BFGS:    3 13:38:01       -0.436523        1.474391\n",
      "BFGS:    4 13:38:01       -0.471680        0.685010\n",
      "BFGS:    5 13:38:01       -0.490967        0.319263\n",
      "BFGS:    6 13:38:01       -0.497803        0.204249\n",
      "BFGS:    7 13:38:02       -0.499756        0.189040\n",
      "BFGS:    8 13:38:02       -0.515625        0.315319\n",
      "BFGS:    9 13:38:02       -0.518066        0.197951\n",
      "BFGS:   10 13:38:02       -0.520020        0.115482\n",
      "BFGS:   11 13:38:02       -0.521484        0.202297\n",
      "BFGS:   12 13:38:02       -0.523926        0.294501\n",
      "BFGS:   13 13:38:02       -0.525391        0.279727\n",
      "BFGS:   14 13:38:02       -0.527344        0.264704\n",
      "BFGS:   15 13:38:02       -0.528320        0.140190\n",
      "BFGS:   16 13:38:02       -0.528809        0.048769\n",
      "      Step     Time          Energy          fmax\n",
      "BFGS:    0 13:38:02       -0.212524        0.952228\n",
      "BFGS:    1 13:38:02       -0.237427        0.920682\n",
      "BFGS:    2 13:38:03       -0.407959        2.176759\n",
      "BFGS:    3 13:38:03       -0.434814        1.620788\n",
      "BFGS:    4 13:38:03       -0.447510        0.246929\n",
      "BFGS:    5 13:38:03       -0.452881        0.293983\n",
      "BFGS:    6 13:38:03       -0.456787        0.285129\n",
      "BFGS:    7 13:38:03       -0.466797        0.526403\n",
      "BFGS:    8 13:38:03       -0.470947        0.259934\n",
      "BFGS:    9 13:38:03       -0.472900        0.138493\n",
      "BFGS:   10 13:38:03       -0.474121        0.241114\n",
      "BFGS:   11 13:38:03       -0.475586        0.331088\n",
      "BFGS:   12 13:38:03       -0.478760        0.291795\n",
      "BFGS:   13 13:38:04       -0.480225        0.160727\n",
      "BFGS:   14 13:38:04       -0.481445        0.074167\n",
      "BFGS:   15 13:38:04       -0.483887        0.151993\n",
      "BFGS:   16 13:38:04       -0.483643        0.239781\n",
      "BFGS:   17 13:38:04       -0.486328        0.248162\n",
      "BFGS:   18 13:38:04       -0.488525        0.137442\n",
      "BFGS:   19 13:38:04       -0.488525        0.062010\n",
      "BFGS:   20 13:38:04       -0.490234        0.109235\n",
      "BFGS:   21 13:38:04       -0.491455        0.161626\n",
      "BFGS:   22 13:38:04       -0.490967        0.172981\n",
      "BFGS:   23 13:38:05       -0.492676        0.115052\n",
      "BFGS:   24 13:38:05       -0.493652        0.050687\n",
      "BFGS:   25 13:38:05       -0.493652        0.068832\n",
      "BFGS:   26 13:38:05       -0.493408        0.102720\n",
      "BFGS:   27 13:38:05       -0.494141        0.142565\n",
      "BFGS:   28 13:38:05       -0.495361        0.150022\n",
      "BFGS:   29 13:38:05       -0.495605        0.080363\n",
      "BFGS:   30 13:38:05       -0.498047        0.061205\n",
      "BFGS:   31 13:38:05       -0.499756        0.052481\n",
      "BFGS:   32 13:38:05       -0.500977        0.047901\n",
      "      Step     Time          Energy          fmax\n",
      "BFGS:    0 13:38:05       -0.208130        1.027455\n",
      "BFGS:    1 13:38:06       -0.235474        1.395095\n",
      "BFGS:    2 13:38:06       -0.347900        1.882796\n",
      "BFGS:    3 13:38:06       -0.391602        1.869784\n",
      "BFGS:    4 13:38:06       -0.455078        0.833854\n",
      "BFGS:    5 13:38:06       -0.473877        0.403990\n",
      "BFGS:    6 13:38:06       -0.483887        0.277316\n",
      "BFGS:    7 13:38:06       -0.486328        0.241114\n",
      "BFGS:    8 13:38:06       -0.502930        0.354760\n",
      "BFGS:    9 13:38:06       -0.506348        0.245752\n",
      "BFGS:   10 13:38:06       -0.508301        0.103017\n",
      "BFGS:   11 13:38:06       -0.510254        0.175781\n",
      "BFGS:   12 13:38:06       -0.512695        0.259111\n",
      "BFGS:   13 13:38:07       -0.515625        0.301769\n",
      "BFGS:   14 13:38:07       -0.519531        0.264243\n",
      "BFGS:   15 13:38:07       -0.520508        0.201844\n",
      "BFGS:   16 13:38:07       -0.520020        0.064542\n",
      "BFGS:   17 13:38:07       -0.521973        0.144848\n",
      "BFGS:   18 13:38:07       -0.522461        0.180027\n",
      "BFGS:   19 13:38:07       -0.525391        0.128610\n",
      "BFGS:   20 13:38:07       -0.526855        0.033289\n",
      "      Step     Time          Energy          fmax\n",
      "BFGS:    0 13:38:07       -0.163208        0.720446\n",
      "BFGS:    1 13:38:07       -0.181274        0.734541\n",
      "BFGS:    2 13:38:07       -0.314209        3.366888\n",
      "BFGS:    3 13:38:08       -0.348389        2.761340\n",
      "BFGS:    4 13:38:08       -0.391357        0.518929\n",
      "BFGS:    5 13:38:08       -0.404297        0.810695\n",
      "BFGS:    6 13:38:08       -0.416504        0.472640\n",
      "BFGS:    7 13:38:08       -0.432617        0.374022\n",
      "BFGS:    8 13:38:08       -0.469971        0.750651\n",
      "BFGS:    9 13:38:08       -0.481934        0.632571\n",
      "BFGS:   10 13:38:08       -0.493652        0.176085\n",
      "BFGS:   11 13:38:08       -0.493652        0.180872\n",
      "BFGS:   12 13:38:08       -0.496582        0.158384\n",
      "BFGS:   13 13:38:08       -0.500488        0.199792\n",
      "BFGS:   14 13:38:08       -0.503418        0.192003\n",
      "BFGS:   15 13:38:09       -0.504883        0.156299\n",
      "BFGS:   16 13:38:09       -0.504395        0.047901\n",
      "      Step     Time          Energy          fmax\n",
      "BFGS:    0 13:38:09       -0.219360        0.998290\n",
      "BFGS:    1 13:38:09       -0.244263        0.959126\n",
      "BFGS:    2 13:38:09       -0.448975        0.545310\n",
      "BFGS:    3 13:38:09       -0.445801        0.449066\n",
      "BFGS:    4 13:38:09       -0.455566        0.243319\n",
      "BFGS:    5 13:38:09       -0.457275        0.213025\n",
      "BFGS:    6 13:38:09       -0.463623        0.290116\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "BFGS:    7 13:38:09       -0.471436        0.264474\n",
      "BFGS:    8 13:38:09       -0.475098        0.159584\n",
      "BFGS:    9 13:38:10       -0.475586        0.107723\n",
      "BFGS:   10 13:38:10       -0.476074        0.159631\n",
      "BFGS:   11 13:38:10       -0.479004        0.233788\n",
      "BFGS:   12 13:38:10       -0.482178        0.207215\n",
      "BFGS:   13 13:38:10       -0.484619        0.130786\n",
      "BFGS:   14 13:38:10       -0.485596        0.086004\n",
      "BFGS:   15 13:38:10       -0.486816        0.089886\n",
      "BFGS:   16 13:38:10       -0.490967        0.124327\n",
      "BFGS:   17 13:38:10       -0.493652        0.148129\n",
      "BFGS:   18 13:38:10       -0.495361        0.099667\n",
      "BFGS:   19 13:38:10       -0.495850        0.063439\n",
      "BFGS:   20 13:38:10       -0.497314        0.060358\n",
      "BFGS:   21 13:38:11       -0.497803        0.067291\n",
      "BFGS:   22 13:38:11       -0.499756        0.070204\n",
      "BFGS:   23 13:38:11       -0.500977        0.068666\n",
      "BFGS:   24 13:38:11       -0.501953        0.045680\n"
     ]
    }
   ],
   "source": [
    "checkpoint_path = \"your-path-here.pt\"\n",
    "os.makedirs(f\"data/{bulk}_{adsorbate}\", exist_ok=True)\n",
    "\n",
    "# Define the calculator\n",
    "calc = OCPCalculator(checkpoint_path=checkpoint_path, cpu = False) # if you dont have a gpu, change to `cpu=True` to run on cpu\n",
    "\n",
    "adslabs = [*heuristic_adslabs.atoms_list, *random_adslabs.atoms_list]\n",
    "# Set up the calculator\n",
    "for idx, adslab in enumerate(adslabs):\n",
    "    adslab.calc = calc\n",
    "    opt = BFGS(adslab, trajectory=f\"data/{bulk}_{adsorbate}/{idx}.traj\")\n",
    "    opt.run(fmax=0.05, steps=100) # For the AdsorbML results we used fmax = 0.02 and steps = 300, but we will use less strict values for brevity."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5bd48f41-52d9-475f-9b11-ec48fc0dc529",
   "metadata": {},
   "source": [
    "# Parse the trajectories and post-process\n",
    "\n",
    "As a post-processing step we check to see if:\n",
    "1. the adsorbate desorbed\n",
    "2. the adsorbate disassociated\n",
    "3. the adsorbate intercalated\n",
    "4. the surface has changed\n",
    "\n",
    "We check these because they effect our referencing scheme and may result in erroneous energies. For (4), the relaxed surface should really be supplied as well. It will be necessary when correcting the SP / RX energies later. Since we don't have it here, we will ommit supplying it, and the detector will instead compare the initial and final slab from the adsorbate-slab relaxation trajectory. If a relaxed slab is provided, the detector will compare it and the slab after the adsorbate-slab relaxation. The latter is more correct! Note: for the results in the AdsorbML paper, we did not check if the adsorbate was intercalated (`is_adsorbate_intercalated()`) because it is a new addition."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "0d005711-c702-4362-b930-f5bb8a6bbc55",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Iterate over trajs to extract results\n",
    "results = []\n",
    "for file in glob(f\"data/{bulk}_{adsorbate}/*.traj\"):\n",
    "    rx_id = file.split(\"/\")[-1].split(\".\")[0]\n",
    "    traj = ase.io.read(file, \":\")\n",
    "    \n",
    "    # Check to see if the trajectory is anomolous\n",
    "    initial_atoms = traj[0]\n",
    "    final_atoms = traj[-1]\n",
    "    atom_tags = initial_atoms.get_tags()\n",
    "    detector = DetectTrajAnomaly(initial_atoms, final_atoms, atom_tags)\n",
    "    anom = (\n",
    "        detector.is_adsorbate_dissociated()\n",
    "        or detector.is_adsorbate_desorbed()\n",
    "        or detector.has_surface_changed()\n",
    "        or detector.is_adsorbate_intercalated()\n",
    "    )\n",
    "    rx_energy = traj[-1].get_potential_energy()\n",
    "    results.append({\"relaxation_idx\": rx_id, \"relaxed_atoms\": traj[-1],\n",
    "                    \"relaxed_energy_ml\": rx_energy, \"anomolous\": anom})"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "ad52f34b-a885-4950-b040-b782acf0b899",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>relaxation_idx</th>\n",
       "      <th>relaxed_atoms</th>\n",
       "      <th>relaxed_energy_ml</th>\n",
       "      <th>anomolous</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>2</td>\n",
       "      <td>(Atom('Cu', [-1.3000465215529715, 2.2517466275...</td>\n",
       "      <td>-0.523438</td>\n",
       "      <td>False</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>6</td>\n",
       "      <td>(Atom('Cu', [-1.3000465215529715, 2.2517466275...</td>\n",
       "      <td>-0.504395</td>\n",
       "      <td>False</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>1</td>\n",
       "      <td>(Atom('Cu', [-1.3000465215529715, 2.2517466275...</td>\n",
       "      <td>-0.567383</td>\n",
       "      <td>False</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>3</td>\n",
       "      <td>(Atom('Cu', [-1.3000465215529715, 2.2517466275...</td>\n",
       "      <td>-0.528809</td>\n",
       "      <td>False</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4</th>\n",
       "      <td>7</td>\n",
       "      <td>(Atom('Cu', [-1.3000465215529715, 2.2517466275...</td>\n",
       "      <td>-0.501953</td>\n",
       "      <td>False</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>5</th>\n",
       "      <td>5</td>\n",
       "      <td>(Atom('Cu', [-1.3000465215529715, 2.2517466275...</td>\n",
       "      <td>-0.526855</td>\n",
       "      <td>False</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>6</th>\n",
       "      <td>0</td>\n",
       "      <td>(Atom('Cu', [-1.3000465215529715, 2.2517466275...</td>\n",
       "      <td>-0.567383</td>\n",
       "      <td>False</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>7</th>\n",
       "      <td>4</td>\n",
       "      <td>(Atom('Cu', [-1.3000465215529715, 2.2517466275...</td>\n",
       "      <td>-0.500977</td>\n",
       "      <td>False</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "  relaxation_idx                                      relaxed_atoms  \\\n",
       "0              2  (Atom('Cu', [-1.3000465215529715, 2.2517466275...   \n",
       "1              6  (Atom('Cu', [-1.3000465215529715, 2.2517466275...   \n",
       "2              1  (Atom('Cu', [-1.3000465215529715, 2.2517466275...   \n",
       "3              3  (Atom('Cu', [-1.3000465215529715, 2.2517466275...   \n",
       "4              7  (Atom('Cu', [-1.3000465215529715, 2.2517466275...   \n",
       "5              5  (Atom('Cu', [-1.3000465215529715, 2.2517466275...   \n",
       "6              0  (Atom('Cu', [-1.3000465215529715, 2.2517466275...   \n",
       "7              4  (Atom('Cu', [-1.3000465215529715, 2.2517466275...   \n",
       "\n",
       "   relaxed_energy_ml  anomolous  \n",
       "0          -0.523438      False  \n",
       "1          -0.504395      False  \n",
       "2          -0.567383      False  \n",
       "3          -0.528809      False  \n",
       "4          -0.501953      False  \n",
       "5          -0.526855      False  \n",
       "6          -0.567383      False  \n",
       "7          -0.500977      False  "
      ]
     },
     "execution_count": 6,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "df = pd.DataFrame(results)\n",
    "df"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "3cec27e4-ea87-468b-8088-389ba7635035",
   "metadata": {},
   "outputs": [],
   "source": [
    "#scrap anomalies\n",
    "df = df[~df.anomolous].copy().reset_index()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c1b71df2-8742-4789-bbbf-1654a5a174e4",
   "metadata": {},
   "source": [
    "# (Optional) Deduplicate structures\n",
    "We may have enumerated very similar structures or structures may have relaxed to the same configuration. For this reason, it is advantageous to cull systems if they are very similar. This results in marginal improvements in the recall metrics we calculated for AdsorbML, so it wasnt implemented there. It is, however, a good way to prevent wasteful VASP calculations. You can also imagine that if we would have enumerated 1000 configs per slab adsorbate combo rather than 100 for AdsorbML, it is more likely that having redundant systems would reduce performance, so its a good thing to keep in mind. This may be done by eye for a small number of systems, but with many systems it is easier to use an automated approach. Here is an example of one such approach, which uses a SOAP descriptor to find similar systems."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "cd356d23-4b5e-4aaf-8f04-eb99ab96394f",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Extract the configs and their energies\n",
    "def deduplicate(configs_for_deduplication: list,\n",
    "                adsorbate_binding_index: int,\n",
    "                cosine_similarity = 1e-3,\n",
    "               ):\n",
    "    \"\"\"\n",
    "    A function that may be used to deduplicate similar structures.\n",
    "    Among duplicate entries, the one with the lowest energy will be kept.\n",
    "    \n",
    "    Args:\n",
    "        configs_for_deduplication: a list of ML relaxed adsorbate-\n",
    "            surface configurations.\n",
    "        cosine_similarity: The cosine simularity value above which,\n",
    "            configurations are considered duplicate.\n",
    "            \n",
    "    Returns:\n",
    "        (list): the indices of configs which should be kept as non-duplicate\n",
    "    \"\"\"\n",
    "    \n",
    "    energies_for_deduplication = np.array([atoms.get_potential_energy() for atoms in configs_for_deduplication])\n",
    "    # Instantiate the soap descriptor\n",
    "    soap = SOAP(\n",
    "        species=np.unique(configs_for_deduplication[0].get_chemical_symbols()),\n",
    "        r_cut = 2.0,\n",
    "        n_max=6,\n",
    "        l_max=3,\n",
    "        periodic=True,\n",
    "    )\n",
    "    #Figure out which index cooresponds to \n",
    "    ads_len = list(configs_for_deduplication[0].get_tags()).count(2)\n",
    "    position_idx = -1*(ads_len-adsorbate_binding_index)\n",
    "    # Iterate over the systems to get the SOAP vectors\n",
    "    soap_desc = []\n",
    "    for config in configs_for_deduplication:\n",
    "        soap_ex = soap.create(config, centers=[position_idx])\n",
    "        soap_desc.extend(soap_ex)\n",
    "\n",
    "    soap_descs = np.vstack(soap_desc)\n",
    "\n",
    "    #Use euclidean distance to assess similarity\n",
    "    distance = squareform(pdist(soap_descs, metric=\"cosine\"))\n",
    "\n",
    "    bool_matrix = np.where(distance <= cosine_similarity, 1, 0)\n",
    "    # For configs that are found to be similar, just keep the lowest energy one\n",
    "    idxs_to_keep = []\n",
    "    pass_idxs = []\n",
    "    for idx, row in enumerate(bool_matrix):\n",
    "        if idx in pass_idxs:\n",
    "            continue\n",
    "            \n",
    "        elif sum(row) == 1:\n",
    "            idxs_to_keep.append(idx)\n",
    "        else:\n",
    "            same_idxs = [row_idx for row_idx, val in enumerate(row) if val == 1]\n",
    "            pass_idxs.extend(same_idxs)\n",
    "            # Pick the one with the lowest energy by ML\n",
    "            min_e = min(energies_for_deduplication[same_idxs])\n",
    "            idxs_to_keep.append(list(energies_for_deduplication).index(min_e))\n",
    "    return idxs_to_keep"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "a1dfa417-02fe-4c66-b06a-aeab56f702e0",
   "metadata": {},
   "outputs": [],
   "source": [
    "configs_for_deduplication =  df.relaxed_atoms.tolist()\n",
    "idxs_to_keep = deduplicate(configs_for_deduplication, adsorbate.binding_indices[0])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "729be6f0-22b9-4d10-860b-39d7d8209ec2",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[0, 1, 2, 3]\n"
     ]
    },
    {
     "data": {
      "text/html": [
       "\n",
       "        <iframe\n",
       "            width=\"1000px\"\n",
       "            height=\"500px\"\n",
       "            src=\"x3dase.html\"\n",
       "            frameborder=\"0\"\n",
       "            allowfullscreen\n",
       "            \n",
       "        ></iframe>\n",
       "        "
      ],
      "text/plain": [
       "<IPython.lib.display.IFrame at 0x7fdfecc60b20>"
      ]
     },
     "execution_count": 10,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# Flip through your configurations to check them out (and make sure deduplication looks good)\n",
    "print(idxs_to_keep)\n",
    "view_x3d_n(configs_for_deduplication[2].repeat((2,2,1)))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "1fd2c12e-d355-4213-b100-81a4054cbbcf",
   "metadata": {},
   "outputs": [],
   "source": [
    "df = df.iloc[idxs_to_keep]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "04b87513-8397-4216-a567-ee81a25cef41",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "The lowest 5 energies are: [-0.567 -0.529 -0.523 -0.504]\n"
     ]
    },
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>index</th>\n",
       "      <th>relaxation_idx</th>\n",
       "      <th>relaxed_atoms</th>\n",
       "      <th>relaxed_energy_ml</th>\n",
       "      <th>anomolous</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>0</td>\n",
       "      <td>2</td>\n",
       "      <td>(Atom('Cu', [-1.3000465215529715, 2.2517466275...</td>\n",
       "      <td>-0.523438</td>\n",
       "      <td>False</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>1</td>\n",
       "      <td>6</td>\n",
       "      <td>(Atom('Cu', [-1.3000465215529715, 2.2517466275...</td>\n",
       "      <td>-0.504395</td>\n",
       "      <td>False</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>2</td>\n",
       "      <td>1</td>\n",
       "      <td>(Atom('Cu', [-1.3000465215529715, 2.2517466275...</td>\n",
       "      <td>-0.567383</td>\n",
       "      <td>False</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>3</td>\n",
       "      <td>3</td>\n",
       "      <td>(Atom('Cu', [-1.3000465215529715, 2.2517466275...</td>\n",
       "      <td>-0.528809</td>\n",
       "      <td>False</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "   index relaxation_idx                                      relaxed_atoms  \\\n",
       "0      0              2  (Atom('Cu', [-1.3000465215529715, 2.2517466275...   \n",
       "1      1              6  (Atom('Cu', [-1.3000465215529715, 2.2517466275...   \n",
       "2      2              1  (Atom('Cu', [-1.3000465215529715, 2.2517466275...   \n",
       "3      3              3  (Atom('Cu', [-1.3000465215529715, 2.2517466275...   \n",
       "\n",
       "   relaxed_energy_ml  anomolous  \n",
       "0          -0.523438      False  \n",
       "1          -0.504395      False  \n",
       "2          -0.567383      False  \n",
       "3          -0.528809      False  "
      ]
     },
     "execution_count": 12,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "low_e_values = np.round(df.sort_values(by = \"relaxed_energy_ml\").relaxed_energy_ml.tolist()[0:5],3)\n",
    "print(f\"The lowest 5 energies are: {low_e_values}\")\n",
    "df"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "fe926290-3bec-406c-83d3-fcb2eee76c0a",
   "metadata": {},
   "source": [
    "# Write VASP input files\n",
    "\n",
    "This assumes you have access to VASP pseudopotentials. The default VASP flags (which are equivalent to those used to make OC20) are located in `ocdata.utils.vasp`. Alternatively, you may pass your own vasp flags to the `write_vasp_input_files` function as `vasp_flags`"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "id": "5baeaa19-bc79-424c-8d1f-9b4a066a486c",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Grab the 5 systems with the lowest energy\n",
    "configs_for_dft = df.sort_values(by = \"relaxed_energy_ml\").relaxed_atoms.tolist()[0:5]\n",
    "config_idxs = df.sort_values(by = \"relaxed_energy_ml\").relaxation_idx.tolist()[0:5]\n",
    "\n",
    "# Write the inputs\n",
    "for idx, config in enumerate(configs_for_dft):\n",
    "    os.mkdir(f\"data/{config_idxs[idx]}\")\n",
    "    write_vasp_input_files(config, outdir = f\"data/{config_idxs[idx]}/\")"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.9.15"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
